﻿using System.Web;
using System.Collections.Generic;
using System.Text.RegularExpressions;


/*On the context.Request object, here's an example of what you'll find:
    Path=/user/rest/
    QueryString=_dc=12312351535&start=0&limit=10
    RawUrl=/rest/user/?_dc=12312351535&start=0&limit=10
    RequestType="GET"
    URL=http://localhost:28624/user/rest/?_dc=12312351535&start=0&limit=10
    Headers["Host"]=http://localhost:28624
 */

namespace Sissonix.ExtRest.WADL
{
    public class WADLContext
    {
        class OverallMatch
        {
            public List<ResourcesMatch> matchedResourcesList = new List<ResourcesMatch>();
            public List<HttpStatusCode> errorCodes = new List<HttpStatusCode>();
        }
        class ResourcesMatch {
            public resources resourcesElement;
            public string urlLeft;
            public List<ResourceMatch> resourceMatchList = new List<ResourceMatch>();
            public List<HttpStatusCode> errorCodes = new List<HttpStatusCode>();
        }
        class ResourceMatch
        {
            public ResourcesMatch parentResourcesMatch;
            public ResourceMatch parentResourceMatch;
            public resource resourceElement;
            public string urlLeft;
            public Dictionary<string, string> tokens = new Dictionary<string, string>();
            public List<HttpStatusCode> errorCodes = new List<HttpStatusCode>();
            public List<method> methodChildren = new List<method>();
        }
        class MethodMatch
        {
            public ResourceMatch parentResouceMatch;
            public method methodElement;
            public List<request> requestChildren = new List<request>();
            public List<HttpStatusCode> errorCodes = new List<HttpStatusCode>();
        }


        public WADLContext()
        {
            HttpRequest req = HttpContext.Current.Request;
            application app = WADLManager.Singleton.Application;
            OverallMatch overall = new OverallMatch();

            /*There is a lot going on in this code.  We need to evaluate all the <resource>
             * tags from the *.wadl file to find the absolute best match.  More than one 
             * <resource> definition may match.  In that case, we need to have
             * business rules that are used to "break ties".
             *
             * To find the match, the following must be taken into consideration:
             * 1. Nested resource paths (including {token} placeholders)
             * 2. Param validation (including required flag)
             * 3. Allowed method (GET/POST/PUT/DELETE)
             * 4. Possible request *.xsd validation
             * 5. Requested media type (from the HTTP Accept header variable)
             * 
             * If a match can't be found, an appropriate response should go back.
             * 400 BAD REQUEST - User's form post didn't match validation (*.xsd or <option> values)
             * 401 UNAUTHORIZED - Credentials are required (since not provided)
             * 403 FORBIDDEN - Credential that were supplied are invalid
             * 404 NOT FOUND - no resource found
             * 405 METHOD NOT ALLOWED - a resource was found but didn't accept that method
             * 406 NOT ACCEPTABLE - A resource was found but couldn't deliver the requested media type
             * 409 CONFLICT - Record state has changed
             * 410 GONE - trying to delete a resource that is no longer there
             * 415 UNSUPPORTED MEDIA TYPE - a resouce was found but can't deliver media type requested
             */

            /**************************************************
             * FIND MATCHING <RESOURCES> OBJECTS
             **************************************************/
            string remainingURL;

            string url = req.Url.ToString().StripFirstLastDelims().FixDelims().ToLower();
            url = Regex.Replace(url, @"\?.*", "");

            /*See if we can find a <resources> tag with a base attribute that
             * includes a full url with http:// or https// for example */
            resources rr;
            string b;

            for (int a = 0; a < app.resources.Length; a++)
            {
                rr = app.resources[a];
                b = rr.@base.StripFirstLastDelims().FixDelims().ToLower();
                if (url.StartsWith(b))
                {
                    remainingURL = url.Remove(0, b.Length);
                    overall.matchedResourcesList.Add(new ResourcesMatch(){ resourcesElement=rr, urlLeft=remainingURL });
                    break;
                }
            }

            if (overall.matchedResourcesList.Count == 0)
            {
                /*Ok, no <resources> tag matched based on a full url.  
                 * Now let's try with with relative paths. */
                string rawurl = req.RawUrl.StripFirstLastDelims().FixDelims().ToLower();
                rawurl = Regex.Replace(rawurl, @"\?.*", "");
                for (int a = 0; a < app.resources.Length; a++)
                {
                    rr = app.resources[a];
                    b = rr.@base.StripFirstLastDelims().FixDelims().ToLower();
                    if (rawurl.StartsWith(b))
                    {
                        remainingURL = rawurl.Remove(0, b.Length);
                        overall.matchedResourcesList.Add(new ResourcesMatch(){ resourcesElement=rr, urlLeft=remainingURL });
                    }
                }
            }

            if (overall.matchedResourcesList.Count == 0)
            {
                overall.errorCodes.Add(HttpStatusCode.NotFound_404);
                return;
            }

            /**************************************************
             * FIND MATCHING <RESOURCE> OBJECTS
             **************************************************/
            resource r;
            bool found = false;
            foreach (ResourcesMatch rm in overall.matchedResourcesList)
            {
                rr = rm.resourcesElement;
                remainingURL = rm.urlLeft.StripFirstLastDelims().FixDelims();
                for (int a = 0; a < rr.resource.Length; a++)
                {
                    r = rr.resource[a];
                    FindMatch(rm, new ResourceMatch() { urlLeft = remainingURL }, r);                    
                }
                if (rm.resourceMatchList.Count > 0) 
                    found = true;
            }

            if (!found)
            {
                overall.errorCodes.Add(HttpStatusCode.NotFound_404);
                return;
            }

            /**************************************************
             * CHECK METHODS (ELSE 405 ERROR)
             **************************************************/
            string method = req.HttpMethod.ToUpper();
            foreach (ResourceMatch rm in winners)
            {
                r = rm.resourceElement;
                for (int a = 0; a < r.Items.Length; a++)
                {
                    if (r.Items[a] is method)
                    {
                        method m = (method)r.Items[a];
                        if (m.name == method)
                        {
                            rm.methodChildren.Add(m);
                        }
                    }
                }
                if (rm.methodChildren.Count == 0)
                {
                    rm.errorCodes.Add(HttpStatusCode.MethodNotAllowed_405);
                }
            }
        }

        private void FindMatch(ResourcesMatch rm, ResourceMatch parentMatch, resource r)
        {
            string remainingURL = parentMatch.urlLeft.ToLower().StripFirstLastDelims().FixDelims();
            string path = r.path.ToLower().StripFirstLastDelims().FixDelims();
            string[] pathPieces = path.Split('/');


            string[] remainingPieces = remainingURL.Split('/');
            bool ok = pathPieces.Length <= remainingPieces.Length;
            for (int a = 0; ok && a < pathPieces.Length; a++)
            {
                if (!pathPieces[a].IsWADLIdentifier())
                {
                    ok = (pathPieces[a] == remainingPieces[a]);
                }
            }
            if (ok)
            {
                ResourceMatch newMatch = new ResourceMatch()
                {
                    resourceElement = r,
                    parentResourceMatch = parentMatch
                };

                for (int a = 0; ok && a < pathPieces.Length; a++)
                {
                    if (pathPieces[a].IsWADLIdentifier())
                        newMatch.tokens.Add(pathPieces[a].WADLIdentifier(), remainingPieces[a]);
                }

                if (pathPieces.Length == remainingPieces.Length)
                {
                     rm.resourceMatchList.Add(newMatch);
                }
                else
                {
                    for (int a = pathPieces.Length; a < remainingPieces.Length; a++)
                    {
                        if (a > pathPieces.Length)
                            newMatch.urlLeft += "/";
                        
                        newMatch.urlLeft += remainingPieces[a];
                    }

                    if (r.Items != null)
                    {
                        for (int i = 0; i < r.Items.Length; i++)
                        {
                            if (r.Items[i] is resource)
                                FindMatch(rm, newMatch, ((resource)r.Items[i]));
                        }
                    }
                }

            }


            
        //    string lastPiece = pathPieces[pathPieces.Length - 1];

        //    if (lastPiece.IsWADLIdentifier())
        //    {
        //        string[] remainingPieces = remainingURL.Split('/');
        //        if (remainingPieces.Length == pathPieces.Length)
        //        {

        //        }
        //    }

        //    path = "^" + path;
        //    string expression = Regex.Replace(path, @"\{\w*\}", @"(?<token>\w*?)", RegexOptions.IgnoreCase);

        //    expression = Regex.Replace(path, @"\{\w*\}", delegate(Match m)
        //    {
        //        return @"(?<" + m.ToString().Substring(1, m.ToString().Length - 2) + @">\w*?)";
        //    });

        //    Regex re = new Regex(expression);
        //    Match match = re.Match(remainingURL);

        //    if (match.Success)
        //    {
        //        ResourceMatch newMatch = new ResourceMatch()
        //        {
        //            matchedResource = r,
        //            parentResource = parentMatch
        //        };

        //        for (int counter = 1; counter < match.Groups.Count; counter++)
        //        {
        //            string group_name = re.GroupNameFromNumber(counter);
        //            newMatch.tokens.Add(group_name, match.Groups[counter].Value);
        //        }

        //        newMatch.urlLeft = Regex.Replace(remainingURL, expression, "")
        //            .StripFirstLastDelims().FixDelims();

        //        if (newMatch.urlLeft == "")
        //        {
        //            winners.Add(newMatch);
        //        }
        //        else
        //        {
        //            for (int i = 0; i < r.Items.Length; i++)
        //            {
        //                if (r.Items[i] is resource)
        //                    FindMatch(winners, newMatch, ((resource)r.Items[i]));
        //            }
        //        }
        //    }

        }
    }
}
